package org.nicocube.airain.re;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.nicocube.airain.domain.client.character.Action;
import org.nicocube.airain.domain.client.character.ActionType;
import org.nicocube.airain.domain.client.character.GameCharacter;
import org.nicocube.airain.domain.client.data.StorageException;
import org.nicocube.airain.domain.client.gamedate.GameDate;
import org.nicocube.airain.domain.client.gamedate.GameDateManager;
import org.nicocube.airain.domain.client.gamedate.IncrementalGameDate;
import org.nicocube.airain.domain.client.report.ActionReport;
import org.nicocube.airain.domain.server.criteria.BetweenCriteria;
import org.nicocube.airain.domain.server.criteria.ConstraintCriteria;
import org.nicocube.airain.domain.server.data.OrderedStorage;
import org.nicocube.airain.domain.server.data.Storage;
import org.nicocube.airain.re.proc.ProcessorFactory;
import org.nicocube.airain.re.utils.Alea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author nico
 * @version 0.1
 */
public class Reactor {
	private static final Logger log = LoggerFactory.getLogger(Reactor.class);
	private GameDateManager dateManager;

	private ExecutorService pool;
	private ProcessorFactory processorFactory;
	private final OrderedStorage<GameCharacter> gcStorage;
	private final OrderedStorage<Action> actionStorage;
	private final Storage<ActionReport> reportStorage;

	public Reactor(GameDateManager dateManager,
			ProcessorFactory processorFactory,
			OrderedStorage<GameCharacter> gcStorage,
			OrderedStorage<Action> actionStorage,
			Storage<ActionReport> reportStorage) {
		this.dateManager = dateManager;
		this.processorFactory = processorFactory;
		this.gcStorage = gcStorage;
		this.actionStorage = actionStorage;
		this.reportStorage = reportStorage;
		pool = Executors.newCachedThreadPool();
	}

	public void process() throws StorageException {
		List<Callable<RuleResult>> callables = new LinkedList<Callable<RuleResult>>();

		Alea alea = new Alea();
		
		try {
			// create and start threads
			for (RuleRequest request : getRequests(dateManager.getCurrent(),
					dateManager.getNext())) {
				callables.add(processorFactory.build(request.getGc(),request.getAction(),alea));
			}
		} catch (Exception e) {
			log.error("error while preparing processors", e);
			throw new RuntimeException("error while preparing processors", e);
		}
		List<Future<RuleResult>> results;
		try {
			results = pool.invokeAll(callables);
		} catch (Exception e) {
			log.error("error while invoking processors", e);
			throw new RuntimeException("error while invoking processors", e);
		}

		// treat results

		for (Future<RuleResult> r : results) {
			RuleResult a;
			try {
				a = r.get();
			} catch (Exception e) {
				log.error("error while retrieving processors result", e);
				throw new RuntimeException("error while retrieving processors result", e);
			}

			new IncrementalGameDate(a.getGc().getLivedate()).incHour();
			
			gcStorage.save(a.getGc());
			
			reportStorage.save(a.getActionReport());
		}
	}

	public List<RuleRequest> getRequests(GameDate current, GameDate next)
			throws StorageException {
		
		List<GameCharacter> lgc = gcStorage.retrieve(
					new ConstraintCriteria<GameDate>("livedate",current)	
				);

		List<RuleRequest> res = new ArrayList<RuleRequest>(lgc.size());
		
		for (GameCharacter gc : lgc) {
			Action a = actionStorage.retrieveOne(
					new BetweenCriteria<GameDate>("gameDate", current, next),
					new ConstraintCriteria<Long>("gameCharacterOrder",gc.getOrder()));	
		
			if (a==null) a = Action.build(ActionType.Sleeping, gc.getOrder(), current);
			
			RuleRequest r = new RuleRequest(gc,a);
			
			res.add(r);
		}

		return res;
	}
}
